package com.waijiaojun.tpo.utils;

import com.waijiaojun.tpo.rest.RestServerException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * 微信各种签名工具类
 *
 * @author Administrator
 */
public class WeixinPaySignUtils {

    private static Logger logger = LoggerFactory.getLogger(WeixinPaySignUtils.class);

    public static void main(String[] args) {
        /*Map<String, String> params = new HashMap<String, String>();
        params.put("appid", "wx8888888888888888");
		params.put("mch_id", "1900000109");
		params.put("device_info", "013467007045764");
		params.put("body", "");
		params.put("nonce_str", "5K8264ILTKCH16CQ2502SI8ZNMTM67VS");

		System.out.println(getXmlStr(params,null));*/

        String str = "hello1234";
        System.out.println(getMD5_2(str));
        System.out.println(getMD5(str));

    }

    /**
     * 根据参数生成最终的提交给微信支付接口的xml，此xml中包含sign字段。
     *
     * @param params
     * @return
     */
    public static String getXmlStr(Map<String, String> params, String payApiKey) {
        StringBuffer xmlStr = new StringBuffer();
        xmlStr.append("<xml>\n");
        Set<String> keys = params.keySet();
        for (String key : keys) {
            xmlStr.append("    <").append(key).append("><![CDATA[").append(params.get(key)).append("]]></").append(key).append(">\n");
        }
        xmlStr.append("    <sign><![CDATA[").append(payUniSign(params, payApiKey)).append("]]></sign>\n");

        xmlStr.append("</xml>");
        return xmlStr.toString();
    }

    /**
     * <h3>微信支付签名算法</h3>
     * <P>
     * 签名生成的通用步骤如下：
     * </p>
     * <p>
     * <p>
     * 第一步，设所有发送或者接收到的数据为集合M，将集合M内非空参数值的参数按照参数名ASCII码从小到大排序（字典序），使用URL键值对的格式（
     * 即key1=value1&key2=value2…）拼接成字符串stringA。 特别注意以下重要规则：
     * <ol>
     * <li>◆ 参数名ASCII码从小到大排序（字典序）；</li>
     * <li>◆ 如果参数的值为空不参与签名；</li>
     * <li>◆ 参数名区分大小写；</li>
     * <li>◆ 验证调用返回或微信主动通知签名时，传送的sign参数不参与签名，将生成的签名与该sign值作校验。</li>
     * <li>◆ 微信接口可能增加字段，验证签名时必须支持增加的扩展字段</li>
     * </ol>
     * </p>
     * <p>
     * 第二步，在stringA最后拼接上key得到stringSignTemp字符串，并对stringSignTemp进行MD5运算，
     * 再将得到的字符串所有字符转换为大写，得到sign值signValue。
     * key设置路径：微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
     * </p>
     *
     * @return 参数签名：sign
     */
    public static String payUniSign(Map<String, String> params, String payApiKey) {
        logger.info("payApiKey:"+payApiKey);
        String sign = "";
        String strTmp = "";
        if (StringUtils.isNotBlank(payApiKey)) {
            strTmp = sortToString(params) + "&key=" + payApiKey;
        } else {
            throw new RestServerException("请配置payApiKey");
        }

        logger.info("#1.生成字符串：#2.连接商户key：" + strTmp);
        sign = getMD5_2(strTmp);
        sign = sign.toUpperCase();
        logger.info("#3.md5编码并转成大写：" + sign);

        return sign;
    }


    /**
     * 去除空值和sign参数，进行字典序排序，然后用&拼接
     *
     * @param params
     * @return
     */
    private static String sortToString(Map<String, String> params) {

        // 移除空值 和 sign参数
        removeBlankValue(params);

        // key排序
        Set<String> keys = params.keySet();

        String[] sortedKeys = toArray(keys);
        Arrays.sort(sortedKeys);

        //
        StringBuffer str = new StringBuffer();

        for (int i = 0; i < sortedKeys.length; i++) {
            String key = sortedKeys[i];
            String value = params.get(key);
            str.append(key + "=" + value + "&");
        }

        str.replace(str.lastIndexOf("&"), str.length(), "");

        return str.toString();
    }

    private static String[] toArray(Set<String> keys) {
        String[] sortedKeys = new String[keys.size()];

        int i = 0;
        for (Iterator<String> iterator = keys.iterator(); iterator.hasNext(); ) {
            String key = (String) iterator.next();
            sortedKeys[i] = key;
            i++;
        }
        return sortedKeys;
    }

    /**
     * 移除空值 和 sign参数
     *
     * @param params
     */
    private static void removeBlankValue(Map<String, String> params) {
        //
        if (params.containsKey("sign")) {
            params.remove("sign");
        }

        List<String> emptyValyeKeys = new ArrayList<>();// 存放空元素的key

        Set<String> keys = params.keySet();
        for (Iterator<String> keyIter = keys.iterator(); keyIter.hasNext(); ) {
            String key = keyIter.next();
            String value = params.get(key);
            if (StringUtils.isBlank(value)) {
                emptyValyeKeys.add(key);
            }
        }
        // 移除空元素
        if (emptyValyeKeys.size() > 0) {
            for (String ek : emptyValyeKeys) {
                params.remove(ek);
            }
        }

    }

    /**
     * MD5加密 该算法实现由Bug，如果MD5加密有的结果第一位是0,则0会被去掉，原因就是BigInteger！！！！
     *
     * @param input
     * @return
     */
    @Deprecated
    public static String getMD5(String input) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] messageDigest = md.digest(input.getBytes());
            BigInteger number = new BigInteger(1, messageDigest);
            String hashtext = number.toString(16);
            // Now we need to zero pad it if you actually want the full 32 chars.
           /* while (hashtext.length() < 32) {
                hashtext = "0" + hashtext;
            }*/
            return hashtext;
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    //静态方法，便于作为工具类
    public static String getMD5_2(String plainText) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(plainText.getBytes());
            byte b[] = md.digest();

            int i;

            StringBuffer buf = new StringBuffer("");
            for (int offset = 0; offset < b.length; offset++) {
                i = b[offset];
                if (i < 0)
                    i += 256;
                if (i < 16)
                    buf.append("0");
                buf.append(Integer.toHexString(i));
            }
            //32位加密  
            return buf.toString();
            // 16位的加密  
            //return buf.toString().substring(8, 24);  
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }

    }

}
